package com.geline.mybatisplus.interceptor.inner;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.geline.cloud.util.StringUtils;
import com.geline.mybatisplus.annotation.DSLocked;
import com.geline.mybatisplus.handler.DSLockedHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.cglib.beans.BeanMap;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.Collection;

/**
 * mybatis-plus字符串属性值加解密拦截器: @DSLocked
 * 使用方式：interceptor.addInnerInterceptor(new DSLockedMybatisPlusInterceptor());
 */
@Slf4j
public class DSLockedMybatisPlusInterceptor implements InnerInterceptor {

    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT) {
            this.toEncryptEntity(parameter);
        }else if (sct == SqlCommandType.UPDATE) {
            Object object = parameter;
            if(parameter instanceof MapperMethod.ParamMap){
                MapperMethod.ParamMap map = (MapperMethod.ParamMap) parameter;
                if(map.containsKey("et")){
                    object = map.get("et");
                }
            }
            this.toEncryptEntity(object);
        }
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object result, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if(result!=null){
            if (result instanceof Collection) {
                //基于selectList
                Collection<?> resultList = (Collection<?>) result;
                if (!resultList.isEmpty()) {
                    for (Object obj : resultList) {
                        toDecryptEntity(obj);
                    }
                }
            } else {
                //基于selectOne
                toDecryptEntity(result);
            }
        }
    }

    //解密属性
    private void toDecryptEntity(Object entity){
        Field[] fields = ReflectUtil.getFields(entity.getClass());
        for(Field field : fields){
            DSLocked annotation = field.getAnnotation(DSLocked.class);
            if(annotation != null) {
                this.doDecrypt(entity, field, annotation);
            }
        }
    }

    //加密属性
    private void toEncryptEntity(Object entity){
        if(entity!=null){
            Field[] fields = ReflectUtil.getFields(entity.getClass());
            for(Field field : fields){
                DSLocked annotation = field.getAnnotation(DSLocked.class);
                if(annotation != null) {
                    this.doEncrypt(entity, field, annotation);
                }
            }
        }
    }

    protected void doEncrypt(Object object, Field field, DSLocked annotation) {
        String likeKey = annotation.likeKey();
        BeanMap beanMap = BeanMap.create(object);
        String fieldName = field.getName();
        Object value = beanMap.get(fieldName);
        if(!StrUtil.isBlankIfStr(value)){
            String type = annotation.value();
            if(StringUtils.isBlank(type)){
                type = "MD5";
            }
            DSLockedHandler handler = null;
            if("MD5".equals(type.toUpperCase())){
                handler = new MD5FieldHandler();
            }else if("SM4".equals(type.toUpperCase())){
                handler = new SM4FieldHandler();
            }else{
                throw new RuntimeException("不支持的加解密类型:"+type);
            }
            String newValue = handler.encryptStr(annotation.secretKey(), value.toString());
            beanMap.put(fieldName, newValue);
            log.info("加密 {} ====> {}", fieldName, newValue);
            if(StrUtil.isNotBlank(likeKey)){
                String key = StrUtil.toCamelCase(likeKey);
                String valueStr = encryptLikeStr(handler, annotation.secretKey(), value.toString());
                beanMap.put(key, valueStr);
                log.info("like加密 {} ====> {}", fieldName, valueStr);
            }
        }
    }

    protected void doDecrypt(Object object, Field field, DSLocked annotation) {
        BeanMap beanMap = BeanMap.create(object);
        String fieldName = field.getName();
        Object value = beanMap.get(fieldName);
        if(!StrUtil.isBlankIfStr(value)){
            String type = annotation.value();
            if(StringUtils.isBlank(type)){
                type = "MD5";
            }
            if("MD5".equals(type.toUpperCase())){
                log.debug("MD5不支持解密 {} ====> {}", fieldName);
            }else if("SM4".equals(type.toUpperCase())){
                DSLockedHandler handler = new SM4FieldHandler();
                String newValue = handler.decryptStr(annotation.secretKey(), value.toString());
                beanMap.put(fieldName, newValue);
                log.info("解密 {} ====> {}", fieldName, newValue);
            }else{
                throw new RuntimeException("不支持的加解密类型:"+type);
            }
        }
    }

    protected String encryptLikeStr(DSLockedHandler handler, String secretKey, String text){
        StringBuilder sb = new StringBuilder();
        for (char c : text.toCharArray()) {
            String s = String.valueOf(c);
            sb.append(handler.encryptStr(secretKey, s)).append(",");
        }
        return sb.toString();
    }
}
